/*
 * Copyright 2019 ACINQ SAS
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package fr.acinq.eclair.crypto.keymanager

import fr.acinq.bitcoin.scalacompat.DeterministicWallet._
import fr.acinq.bitcoin.scalacompat.{Block, BlockHash, DeterministicWallet}
import fr.acinq.eclair.channel.ChannelConfig
import fr.acinq.eclair.randomLong
import scodec.bits.ByteVector

/**
 * An implementation of [[ChannelKeyManager]] that supports deterministic derivation of keys, based on an initial
 * random funding key.
 *
 * The resulting paths looks like so on mainnet:
 *
 * {{{
 *  funding txs:
 *     47' / 1' / <fundingKeyPath> / <index>'
 *
 *  commitment basepoint keys (payment, revocation, htlc, etc.):
 *     47' / 1' / <commitmentKeyPath> / <1'-5'>
 * }}}
 *
 * Where the commitmentKeyPath is generated by hashing the funding key at index 0.
 *
 * @param seed seed from which the channel funding keys will be derived.
 */
case class LocalChannelKeyManager(seed: ByteVector, chainHash: BlockHash) extends ChannelKeyManager {
  private val master = DeterministicWallet.generate(seed)

  // We use the following prefix to derive channel funding keys from our seed.
  private val basePath = chainHash match {
    case Block.LivenetGenesisBlock.hash => DeterministicWallet.hardened(47) :: DeterministicWallet.hardened(1) :: Nil
    case _ => DeterministicWallet.hardened(46) :: DeterministicWallet.hardened(1) :: Nil
  }

  /** We always generate a random key path for new channels, which ensures that each channel uses a unique key path. */
  override def newFundingKeyPath(isChannelOpener: Boolean): KeyPath = {
    val last = DeterministicWallet.hardened(if (isChannelOpener) 1 else 0)

    def next(): Long = randomLong() & 0xFFFFFFFFL

    DeterministicWallet.KeyPath(Seq(next(), next(), next(), next(), next(), next(), next(), next(), last))
  }

  override def channelKeys(channelConfig: ChannelConfig, fundingKeyPath: KeyPath): ChannelKeys = {
    val fundingMasterKey = master.derivePrivateKey(basePath ++ fundingKeyPath.path)
    val commitmentMasterKey = if (channelConfig.hasOption(ChannelConfig.FundingPubKeyBasedChannelKeyPath)) {
      // Deterministic mode: use the funding public key itself to compute the key path for commitment keys.
      val fundingPublicKey = fundingMasterKey.derivePrivateKey(hardened(0)).publicKey
      val keyPath = ChannelKeyManager.keyPathFromPublicKey(fundingPublicKey)
      derivePrivateKey(master, basePath ++ keyPath.path)
    } else {
      // Legacy mode: we simply reuse the funding key path as our channel key path.
      // Note that this mode must never be used for new channels as it conflicts with splicing keys.
      fundingMasterKey
    }
    ChannelKeys(fundingMasterKey, commitmentMasterKey)
  }

}
